Skip to content

feat: add CraftDImage component for Android/KMP#103

Open
rviannaoliveira wants to merge 6 commits intomainfrom
feature/add-craftd-image-clean
Open

feat: add CraftDImage component for Android/KMP#103
rviannaoliveira wants to merge 6 commits intomainfrom
feature/add-craftd-image-clean

Conversation

@rviannaoliveira
Copy link
Copy Markdown
Contributor

Summary

  • Adds CraftDImage component to craftd-core, craftd-compose and craftd-xml
  • ImageProperties model com url, contentScale, contentDescription e actionProperties
  • CraftDContentScale enum mapeando para Compose ContentScale
  • CraftDImageBuilder (Compose) com imageLoader injetável — não pré-registrado por design
  • CraftDImageComponentRender (XML) com imageLoader injetável — registrado em CraftDBuilderManager
  • Testes unitários para serialização de ImageProperties, toContentScale() e key do CraftDImageBuilder
  • Docs atualizados: compose.md e view-system.md
  • Sample app com Coil (Compose) e Picasso (XML)
  • Otimizações de contexto: instructions por plataforma em .claude/instructions/, regra de platform: no /propose

Test plan

  • ./gradlew :craftd-core:testDebugUnitTest passes
  • ./gradlew :craftd-compose:testDebugUnitTest passes
  • ./gradlew :app-sample-android:assembleDebug passes
  • CraftDImage renderiza na tela Compose do sample
  • CraftDImage renderiza na tela XML do sample
  • Tap na imagem dispara action (deeplink/analytics)

🤖 Generated with Claude Code

rviannaoliveira and others added 5 commits April 11, 2026 20:09
- Add IMAGE_COMPONENT to CraftDComponentKey enum
- Add CraftDContentScale enum (CROP, FIT, FILL_BOUNDS, FILL_WIDTH, FILL_HEIGHT, INSIDE, NONE)
- Add ImageProperties data class with url, contentScale, contentDescription, actionProperties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Compose:
- Add toContentScale() extension mapping CraftDContentScale → ContentScale
- Add CraftDImage composable with injectable imageLoader lambda
- Add CraftDImageBuilder with injectable imageLoader constructor param

XML:
- Add CraftDImageComponent (AppCompatImageView wrapper)
- Add CraftDImageComponentRender with injectable imageLoader
- Register CraftDImageComponentRender in CraftDBuilderManager via optional imageLoader param

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Unit tests for ImageProperties serialization, toContentScale() and CraftDImageBuilder key
- Updated docs/how-to-use/compose.md and view-system.md with CraftDImage usage examples
- Registered CraftDImageBuilder (Coil) in Compose sample and CraftDImageComponentRender (Picasso) in XML sample
- Added CraftDImage entry to dynamic.json
- Switched app-sample-android to local craftd-xml project dependency
- Added Coil 2.6.0 to libs.versions.toml
- Deleted notes.md after context consumed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor Author

@rviannaoliveira rviannaoliveira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — feat: add CraftDImage component for Android/KMP

Resumo

A implementação está bem estruturada — abstração do loader injetável, separação correta entre módulos, testes e docs presentes. Mas há dois bugs e três problemas que precisam de atenção antes do merge.


Crítico

1. contentScale ignorado silenciosamente

A propriedade contentScale de ImageProperties nunca é usada. A função toContentScale() foi criada mas não é chamada em lugar nenhum.

  • CraftDImage.kt: o imageLoader recebe (url, contentDescription, modifier) — sem ContentScale
  • CraftDImageComponentRender: não mapeia contentScale para ImageView.scaleType
  • O sample app passa AsyncImage sem contentScale

O resultado é que o servidor pode enviar "contentScale": "CROP" e o cliente ignora completamente. A assinatura do imageLoader precisa incluir ContentScale (Compose) ou um parâmetro equivalente.

2. @Stable/@Immutable de androidx.compose.runtime em commonMain — viola regra 4

CraftDContentScale e ImageProperties em craftd-core/commonMain importam androidx.compose.runtime.Stable e androidx.compose.runtime.Immutable. Essas são anotações de plataforma Compose, proibidas em commonMain.

Solução: remover as anotações (são hints de compilador Compose, desnecessárias em código de modelo de um módulo core) ou mover via expect/actual se o impacto de performance realmente justificar.


Moderado

3. Builder Compose não registrado — regra 9

A regra 9 do CLAUDE.md diz que todo novo builder deve ser registrado no CraftDBuilderManager. A justificativa de design (requer imageLoader) é válida, mas diverge da regra sem documentação explícita.

Sugestão: atualizar o CLAUDE.md para documentar essa exceção como padrão ("builders com dependências externas obrigatórias não são pré-registrados"), ou fornecer um imageLoader default no construtor que lance IllegalStateException com mensagem clara.

4. Testes de CraftDImageBuilderTest incompletos — task 4.3

A task 4.3 pedia: "verify imageLoader is called with correct args and actionProperties triggers listener". O teste atual só verifica a key. Falta:

  • Verificar que o imageLoader é invocado com url e contentDescription corretos
  • Verificar que listener.invoke(actionProperties) é chamado quando actionProperties != null

Também não há testes para CraftDImageComponentRender (XML).


Positivo

  • Abstração via lambda injetável está correta — cumpre regra 10 (sem acoplamento a Coil/Picasso)
  • onAction/fallback coberto tanto no Compose quanto no XML
  • ImageProperties no craftd-core com serialização @Serializable correta
  • Docs de compose.md e view-system.md atualizados com exemplos práticos
  • XML: registro condicional (imageLoader?.let { ... }) é boa solução para o CraftDBuilderManager
  • Sample app demonstra ambas as plataformas (Coil no Compose, Picasso no XML)
  • Instructions por plataforma em .claude/instructions/ são uma melhoria valiosa ao CLAUDE.md

Ação necessária antes do merge

  1. Corrigir a assinatura do imageLoader para incluir ContentScale e efetivamente usar properties.contentScale
  2. Remover @Stable/@Immutable de commonMain (CraftDContentScale e ImageProperties)
  3. Complementar os testes do CraftDImageBuilderTest (imageLoader chamado + listener disparado)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 11, 2026

🦙 MegaLinter status: ⚠️ WARNING

Descriptor Linter Files Fixed Errors Warnings Elapsed time
⚠️ KOTLIN detekt yes 189 no 5.49s
⚠️ MARKDOWN markdown-table-formatter 48 1 0 0.32s
⚠️ YAML prettier 18 1 4 0.87s

See detailed report in MegaLinter reports

You could have the same capabilities but better runtime performances if you use a MegaLinter flavor:

MegaLinter is graciously provided by OX Security

implementation("com.squareup.okhttp3:okhttp:4.9.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
implementation("com.squareup.picasso:picasso:2.8")
implementation(libs.coil.compose)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entrou o coil?

else -> Alignment.Top
}

internal fun CraftDContentScale?.toContentScale(): ContentScale = when (this) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extensão de nullable eu acho embaçado, acho que fica mais claro deixar o nullable check para o caller

commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.serialization.json)
implementation(compose.runtime)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Precisa disso?


@Stable
@Immutable
enum class CraftDContentScale {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Não precisa de stable e immutable pra enum

kotlinx-coroutines-test = "1.8.1"

# Coil
coil = "2.6.0"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Precisa dele ?

Copy link
Copy Markdown
Collaborator

@gabrielbmoro gabrielbmoro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remover o coil e permitir que o dev use a biblioteca de image loading que quiser

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants